package grex.Nodes.Depricated;

import grex.Options;
import grex.Data.Prediction;
import grex.Data.PredictionContainer;
import grex.Nodes.GeneException;
import grex.Nodes.GeneException;
import grex.Nodes.ITerminal;
import grex.Nodes.ITerminal;
import grex.Nodes.KnnNeighbour;
import grex.Nodes.Node;
import grex.Nodes.Node;

import java.util.Iterator;
import java.util.TreeSet;

public class LocalKnn extends Node implements ITerminal {

    private static final int NROFCILDREN = 1;
    private int k = -1,  classes;
    private double kPercent;
    private PredictionContainer neigbours;
    private double lowerProb = -1;
    private double instProb = 0;
    private double[] attributeWeights = null;
    private double[] support;
    int count = 0;

    public LocalKnn() {
        super(NROFCILDREN);
        kPercent = Options.rnd.nextDouble();
    }

    public synchronized void execute(Node parent, PredictionContainer data, int mode) throws GeneException {
        this.parent = parent;
        if (mode == Node.TRAIN_MODE) {
            neigbours = data;
            count = data.values().size();
            if (k < 1) {
                double tmp = (kPercent * (ops.getSIMPLIFICATION_BATCH())) + 1;
                k = (int) Math.floor(tmp);
            }
        }
        for (Prediction p : data.values()) {
            support = new double[environment.getNrOfTargetCategories()];
            p.setSupport(support);
            calcValue(p.getInstance());
            p.setLeaf(this);
        }

    }

    private void calcValue(double[] instance) throws GeneException {
        TreeSet<KnnNeighbour> knns;
        if (ops.isKNN_WEIGHTED_ATTRIBUTES()) {
            attributeWeights = ((KnnAttWeights) this.getChildren()[0]).getAttributeWeights();
            knns = getNearestAttrKnns(instance, neigbours);
        } else {
            knns = environment.getKNNs(instance, neigbours);
        }
        if (k > knns.size()) {
            k = (int) (kPercent * (ops.getKNN_MAX() - ops.getKNN_MIN())) + ops.getKNN_MIN();
        }
        if (knns.size() > 1) {
            if (ops.getPROBLEM_TYPE() == ops.CLASSIFICATION) {
                calcSupport(knns);
                return;
            } else {
                double avgPred = 0;
                Iterator i = knns.iterator();
                for (int j = 0; j < k && i.hasNext(); j++) {
                    double[] current = ((KnnNeighbour) i.next()).getInstance();
                    avgPred += current[current.length - 1] / k;
                }
                instProb = 0;
                lowerProb = 0;
                int lowerCount = 0;
                int probCount = 0;
                i = knns.iterator();
                for (int j = 0; j < k && i.hasNext(); j++) {
                    double[] current = ((KnnNeighbour) i.next()).getInstance();
                    double target = current[current.length - 1];
                    if (avgPred > target) {
                        instProb += (Math.abs(target - avgPred) / target);
                        probCount++;
                    } else {
                        lowerProb += (Math.abs(target - avgPred) / target);
                        lowerCount++;
                    }
                }
                if (probCount > 0) {
                    instProb = instProb / probCount;
                }
                if (lowerCount > 0) {
                    lowerProb = lowerProb / lowerCount;
                }

                return;
            }
        }
    }

    private void calcSupport(TreeSet<KnnNeighbour> knns) {
        //calc class Distribution
        double targetValue;
        Iterator i = knns.iterator();
        for (int j = 0; j < k && i.hasNext(); j++) {
            double[] n = ((KnnNeighbour) i.next()).getInstance();
            targetValue = n[n.length - 1];
            support[(int) targetValue]++;
        }
        if (ops.isKNN_WEIGHTED_VOTING()) {
            calcWeightedSupport(knns);
        }
    }

    private void calcWeightedSupport(TreeSet<KnnNeighbour> knns) {
        for(int i = 0;i<support.length;i++)
           support[i]=0;
        double targetValue = 0;
        KnnNeighbour kn;
        Iterator i = knns.iterator();
        double totDist=0;
        for (int j = 0; j < k && i.hasNext(); j++) {
            kn = (KnnNeighbour) i.next();
            double[] n = kn.getInstance();
            targetValue = n[n.length - 1];
            int targetClass = (int) targetValue;
            if(totDist!=0)
                support[targetClass]+=(1-(kn.getDistance()/totDist));
        }
    }

    public void initAttributesWeights() {
        attributeWeights = new double[ops.getNR_OF_INPUT_COLUMNS()];
        for (int i = 0; i < attributeWeights.length; i++) {
            attributeWeights[i] = ops.rnd.nextDouble();
        }
    }

    private TreeSet<KnnNeighbour> getNearestAttrKnns(double[] instance, PredictionContainer neigbours) {
        TreeSet<KnnNeighbour> consideredKnns = new TreeSet<KnnNeighbour>();
        Iterator<Prediction> pc = neigbours.values().iterator();
        for (int z = 0; pc.hasNext() && z < ops.getNR_OF_CONSIDERED_NEIGBHOURS(); z++) {
            double[] n = pc.next().getInstance();
            //Calc weighted distance
            if (n != instance) {
                double attrDist = 0;
                for (int i = 0; i < getAttributeWeights().length; i++) {
                    if (environment.isNumericColumn(i)) {
                        attrDist += Math.pow(instance[i] - n[i], 2) * getAttributeWeights()[i];
                    } else {//Categorical attribute
                        if (instance[i] != n[i]) {
                            attrDist += 1 * getAttributeWeights()[i];
                        }
                    }
                }
                KnnNeighbour k = new KnnNeighbour(n, attrDist);
                consideredKnns.add(k);
            }
        }
        return consideredKnns;

    }

    public double getKPercent() {
        return kPercent;
    }

    public void setKPercent(double kPercent) {
        this.kPercent = kPercent;
    }

    public double getLowerProb() {
        return lowerProb;
    }

    public void setNeigbours(PredictionContainer neigbours) {
        this.neigbours = neigbours;
    }

    public PredictionContainer getNeigbours() {
        return neigbours;
    }

    public void setProb(double prob) {
        this.instProb = prob;
    }

    public void setK(int k) {
        this.k = k;
    }

    public void setCount(int count) {
        this.count = count;
    }

    public Object clone() {
        LocalKnn localKnn = new LocalKnn();

        localKnn.setNeigbours(neigbours.shallowClone());
        localKnn.setOptions(ops);
        localKnn.setEnvironment(environment);
        localKnn.setKPercent(kPercent);
        localKnn.setCount(count);
        localKnn.setProb(instProb);
        localKnn.lowerProb = lowerProb;
        localKnn.setK(k);
        localKnn.setParent(parent);
        cloneChildren(localKnn);
        return localKnn;
    }

    public String toString() {
        String s = "LKNN" + k + "(" + neigbours.size() + ")";
        return s;
    }

    public double getProb() {
        return instProb;
    }

    public int getCount() {
        return count;
    }

    public double getLowProb() {
        return lowerProb;
    }

    public double[] getAttributeWeights() {
        return attributeWeights;
    }

    public void setAttributeWeights(double[] attributeWeights) {
        this.attributeWeights = attributeWeights;
    }
}
